package com.nf.mvc.exception;

import com.nf.mvc.HandlerExceptionResolver;
import com.nf.mvc.MvcContext;
import com.nf.mvc.ViewResult;
import com.nf.mvc.vo.ResponseVO;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.nf.mvc.ViewResult.adaptHandlerResult;
import static com.nf.mvc.exception.ExceptionUtils.getRootCause;
import static com.nf.mvc.handler.HandlerHelper.empty;
import static com.nf.mvc.handler.HandlerHelper.json;

public class ExceptionHandlerExceptionResolver implements HandlerExceptionResolver {

    List<Method> exceptionMethods = new ArrayList<>();

    public ExceptionHandlerExceptionResolver() {
        resolveExceptionHandlerMethods();
    }

    protected void resolveExceptionHandlerMethods() {
        Set<Class<?>> scannedClasses = MvcContext.getMvcContext().getScannedClasses();
        for (Class<?> scannedClass : scannedClasses) {
            Method[] methods = scannedClass.getDeclaredMethods();
            for (Method method : methods) {
                if (isExceptionHandlerMethod(method)) {
                    exceptionMethods.add(method);
                }
            }
        }
    }

    /**
     * handler执行过程中出的异常处理不了有以下两种情况
     * <ul>
     *     <li>没有任何异常处理方法可以处理此异常</li>
     *     <li>异常处理方法处理异常的过程中，它自己出了异常</li>
     * </ul>
     * <p>
     * 为了表明异常处理不了，通过返回null的方式来表示
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @return
     */
    @Override
    public ViewResult resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        Exception rootCause = (Exception) getRootCause(ex);
        Method exceptionHandleMethod = findMostMatchedHandlerMethod(rootCause);//修改
        //表明没有任何方法是可以处理当前异常的
        if (exceptionHandleMethod == null) {
            //返回null doDispatch方法会继续抛异常，进而被service方法捕获处理，
            // 这点跟下面的catch块代码返回null含义是一样的
            return null;
        }
        try {
            Object instance = exceptionHandleMethod.getDeclaringClass().newInstance();
            Object result = exceptionHandleMethod.invoke(instance, rootCause);
            return adaptHandlerResult(result);
        } catch (Exception e) {
            return null;
        }

    }


    private Method findMostMatchedHandlerMethod(Exception ex) {
        //最匹配的方法
        Method mostMatchedMethod = null;
        //匹配的方法
        Method matchedMethod;

        for (Method exceptionMethod : exceptionMethods) {
            //找到方法上修饰的注解中指定的能处理的异常
            Class<? extends Exception> canHandleExceptionClass = exceptionMethod.getDeclaredAnnotation(ExceptionHandler.class).value();
            if (canHandleExceptionClass.isAssignableFrom(ex.getClass())) {
                matchedMethod = exceptionMethod;

                if (mostMatchedMethod == null) {
                    mostMatchedMethod = matchedMethod;
                } else {

                    Class<? extends Exception> mostCanHandleExceptionClass = mostMatchedMethod.getDeclaredAnnotation(ExceptionHandler.class).value();

                    if (mostCanHandleExceptionClass.isAssignableFrom(canHandleExceptionClass)) {
                        mostMatchedMethod = matchedMethod;
                    }
                }
            }

        }

        return mostMatchedMethod;

    }

    private boolean isExceptionHandlerMethod(Method method) {
        if (!method.isAnnotationPresent(ExceptionHandler.class)) {
            return false;
        }
        Class<? extends Exception> exceptionClass = method.getDeclaredAnnotation(ExceptionHandler.class).value();

        return method.getParameterCount() == 1
                && exceptionClass == method.getParameters()[0].getType();
    }
}
